/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.matrixxun.starry;

import com.matrixxun.starry.provider.ListHeaderProvider;

import ohos.agp.components.*;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.global.resource.NotExistException;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.miscservices.timeutility.Time;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Optional;

/**
 * 自定义顶部区域增加图片，并随列表滑动进行图片缩放动画
 */
public class PullToZoomListView extends ListContainer
        implements Component.ScrolledListener, Component.TouchEventListener {
    private static final HiLogLabel LOG_LABEL = new HiLogLabel(HiLog.LOG_APP, 0x20221, "PullToZoomListView");
    private static final int INVALID_VALUE = -1;
    private static float DEFAULT_MIN_SCALE = 1.0f;

    int mActivePointerId = INVALID_VALUE;
    private StackLayout mHeaderContainer;
    private Image mHeaderImage;
    private Image mShadow;

    private int mScreenHeight;
    private int mHeaderHeight;
    private ArrayList<FixedComponentInfo> mHeaderComponentsInfos = new ArrayList<FixedComponentInfo>();
    private ListHeaderProvider mHeaderProvider;
    private FixedComponentInfo mFixedComponentInfo;

    private float mLastMotionY = INVALID_VALUE;
    private float mLastScale = INVALID_VALUE;
    private float mMaxScale = INVALID_VALUE;

    private Component.ScrolledListener mScrolledListener;

    private boolean mScrollable = true;

    private boolean mShowHeaderImage = true;

    private boolean mZoomable = true;

    @Override
    public boolean onTouchEvent(Component component, TouchEvent ev) {
        if (!mZoomable) {
            return true;
        }
        switch (ev.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                HiLog.info(LOG_LABEL, "onTouchEvent : POINT_DOWN");
                if (!mAnimationHandler.mIsFinished) {
                    mAnimationHandler.abortAnimation();
                }
                if (!mAnimationHandler.mIsFinished) {
                    mAnimationHandler.abortAnimation();
                }
                mLastMotionY = ev.getPointerScreenPosition(0).getY();
                mActivePointerId = ev.getPointerId(ev.getIndex());
                mMaxScale = ((float) mScreenHeight) / ((float) mHeaderHeight);
                mLastScale = ((float) mHeaderContainer.getBottom()) / ((float) mHeaderHeight);
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                HiLog.info(LOG_LABEL, "onTouchEvent : POINT_UP");
                reset();
                endScaling();
                break;
            case TouchEvent.POINT_MOVE:
                HiLog.info(LOG_LABEL, "onTouchEvent : POINT_MOVE");
                int activePointerIndex = ev.getPointerId(mActivePointerId);
                if (activePointerIndex != INVALID_VALUE) {
                    if (mLastMotionY == INVALID_VALUE) {
                        mLastMotionY = ev.getPointerScreenPosition(activePointerIndex).getY();
                    }
                    if (mHeaderContainer.getBottom() < mHeaderHeight) {
                        mLastMotionY = ev.getPointerScreenPosition(activePointerIndex).getY();
                        break;
                    }
                    updateContainerConfig(ev);
                }
                break;
            case TouchEvent.OTHER_POINT_DOWN:
                int index = ev.getIndex();
                mLastMotionY = ev.getPointerScreenPosition(index).getY();
                mActivePointerId = ev.getPointerId(index);
                break;
            case TouchEvent.OTHER_POINT_UP:
                onSecondaryPointerUp(ev);
                mLastMotionY = ev.getPointerScreenPosition(ev.getPointerId(mActivePointerId)).getY();
                break;
        }
        return true;
    }

    private boolean updateContainerConfig(TouchEvent ev) {
        LayoutConfig params = mHeaderContainer.getLayoutConfig();
        float scale =
                ((((((float) mHeaderContainer.getBottom()) + (ev.getPointerScreenPosition(0).getY() - mLastMotionY))
                                                / ((float) mHeaderHeight))
                                        - mLastScale)
                                / 2.0f)
                        + mLastScale;
        if (((double) mLastScale) > 1.0d || scale >= mLastScale) {
            mLastScale = Math.min(Math.max(scale, DEFAULT_MIN_SCALE), mMaxScale * 2 / 3);
            params.height = (int) (((float) mHeaderHeight) * mLastScale);
            if (params.height < mScreenHeight) {
                mHeaderContainer.setLayoutConfig(params);
            }
            mLastMotionY = ev.getPointerScreenPosition(0).getY();
            if (mHeaderProvider != null) {
                mHeaderProvider.notifyDataChanged();
            }
            return true;
        }
        params.height = mHeaderHeight;
        mHeaderContainer.setLayoutConfig(params);
        if (mHeaderProvider != null) {
            mHeaderProvider.notifyDataChanged();
        }
        return true;
    }

    private float getInterpolation(float scaleKeepValue) {
        scaleKeepValue -= DEFAULT_MIN_SCALE;
        float scale =
                (scaleKeepValue * scaleKeepValue * scaleKeepValue * scaleKeepValue * scaleKeepValue)
                        + DEFAULT_MIN_SCALE;
        return scale;
    }

    private AnimationHandler mAnimationHandler =
            new AnimationHandler(EventRunner.getMainEventRunner()) {
                @Override
                protected void processEvent(InnerEvent event) {
                    HiLog.info(LOG_LABEL, "processEvent :" + event.eventId);
                    if (!mIsFinished && ((double) mScale) > 1.0d) {
                        float scale =
                                mScale
                                        - ((mScale - DEFAULT_MIN_SCALE)
                                                * getInterpolation(
                                                        (((float) Time.getCurrentThreadTime()) - ((float) mStartTime))
                                                                / ((float) mDuration)));
                        LayoutConfig params = mHeaderContainer.getLayoutConfig();
                        if (scale <= DEFAULT_MIN_SCALE) {
                            mIsFinished = true;
                            params.height = mHeaderHeight;
                            mAnimationHandler.removeEvent(0);
                        } else {
                            params.height = (int) (((float) mHeaderHeight) * scale);
                            mAnimationHandler.sendEvent(0, 10);
                        }
                        mHeaderContainer.setLayoutConfig(params);
                        if (mHeaderProvider != null) {
                            mHeaderProvider.notifyDataChanged();
                        }
                    }
                }
            };

//    @Override
//    protected boolean onArrange(int left, int top, int width, int height) {
//        if (mHeaderHeight == 0) {
//            mHeaderHeight = mHeaderContainer.getHeight();
//        }
//        return super.onArrange(left, top, width, height);
//    }

    class AnimationHandler extends EventHandler {
        long mDuration;
        boolean mIsFinished = true;
        float mScale;
        long mStartTime;

        AnimationHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
        }

        /**
         * 图片缩放动画是否完成
         *
         * @return true：完成；false：未完成
         */
        public boolean isFinished() {
            return mIsFinished;
        }

        void abortAnimation() {
            HiLog.info(LOG_LABEL, "abortAnimation~~~~~~");
            mIsFinished = true;
        }

        void startAnimation(long duration) {
            HiLog.info(LOG_LABEL, "startAnimation~~~~~~");
            mStartTime = Time.getCurrentThreadTime();
            mDuration = duration;
            mScale = ((float) mHeaderContainer.getBottom()) / ((float) mHeaderHeight);
            mIsFinished = false;
        }
    }

    public PullToZoomListView(Context context) {
        this(context, null);
    }

    public PullToZoomListView(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public PullToZoomListView(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init(context);
    }

    private void init(Context context) {
        HiLog.info(LOG_LABEL, "PullToZoomListView INIT~~~~~~");
//        DeviceCapability deviceCapability = context.getResourceManager().getDeviceCapability();
//        int screenScale = deviceCapability.screenDensity / 160;
//        HiLog.info(LOG_LABEL,"screenScale : " + screenScale);
//        HiLog.info(
//                LOG_LABEL,
//                "deviceCapability.width : " + deviceCapability.width + ",screenDensity: " + deviceCapability.screenDensity);
//        int width = deviceCapability.width * screenScale;
//        HiLog.info(LOG_LABEL, "deviceCapability.width : " + width);
//        mScreenHeight = deviceCapability.height * screenScale;
        Optional<Display>
                display = DisplayManager.getInstance().getDefaultDisplay(this.getContext());
        int width = display.get().getAttributes().width;
        mScreenHeight = display.get().getAttributes().height;

        mHeaderContainer = new StackLayout(context);
        setHeaderViewSize(width, (int) ((((float) width) / 16.0f) * 9.0f));
        mHeaderImage = new Image(context);
        StackLayout.LayoutConfig headParams =
                new StackLayout.LayoutConfig(
                        StackLayout.LayoutConfig.MATCH_PARENT, StackLayout.LayoutConfig.MATCH_PARENT);
        headParams.alignment = LayoutAlignment.CENTER;
        mHeaderImage.setLayoutConfig(headParams);
        mHeaderContainer.addComponent(mHeaderImage);
        mShadow = new Image(context);

        mFixedComponentInfo = new FixedComponentInfo();
        mFixedComponentInfo.component = mHeaderContainer;

        super.setScrolledListener(this);
        super.setTouchEventListener(this);
    }

    /**
     * 添加顶部区域provider
     *
     * @param context      上下文
     * @param listProvider 列表provider
     */
    public void setHeaderProvider(Context context, BaseItemProvider listProvider) {
        mHeaderComponentsInfos.clear();
        mHeaderComponentsInfos.add(mFixedComponentInfo);
        mHeaderProvider = new ListHeaderProvider(context, mHeaderComponentsInfos, listProvider);
        this.setItemProvider(mHeaderProvider);
    }

    /**
     * 获取顶部图片对象
     *
     * @return Image对象
     */
    public Image getHeaderView() {
        return mHeaderImage;
    }

    private Optional<PixelMap> getPixelMap(Context context, int id) {
        try {
            String path = context.getResourceManager().getMediaPath(id);
            RawFileEntry assetManager = context.getResourceManager().getRawFileEntry(path);
            ImageSource.SourceOptions options = new ImageSource.SourceOptions();
            options.formatHint = "image/png";
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            Resource asset = assetManager.openRawFile();
            ImageSource source = ImageSource.create(asset, options);
            return Optional.ofNullable(source.createPixelmap(decodingOptions));
        } catch (NotExistException | IOException | WrongTypeException e) {
            HiLog.info(LOG_LABEL, e.getMessage());
        }
        return Optional.empty();
    }

    /**
     * 给顶部区域的底部位置设置阴影图片
     *
     * @param resId 阴影图片的资源ID
     */
    public void setShadow(int resId) {
        if (mShowHeaderImage) {
            try {
                mShadow.setBackground(new PixelMapElement(getResourceManager().getResource(resId)));
                StackLayout.LayoutConfig params =
                        new StackLayout.LayoutConfig(
                                StackLayout.LayoutConfig.MATCH_PARENT,
                                getPixelMap(mContext, resId).get().getImageInfo().size.height);
                params.alignment = LayoutAlignment.BOTTOM;
                mShadow.setLayoutConfig(params);
                mHeaderContainer.addComponent(mShadow);
                HiLog.info(LOG_LABEL, "getShadowResource : " + getResourceManager().getResource(resId));
            } catch (NotExistException | IOException e) {
                HiLog.info(LOG_LABEL, e.getMessage());
            }
        }
    }

    /**
     * 设置顶部区域默认的宽高
     *
     * @param width  默认宽度
     * @param height 默认高度
     */
    public void setHeaderViewSize(int width, int height) {
        if (mShowHeaderImage) {
            HiLog.info(LOG_LABEL, "setHeaderViewSize : " + width + "," + height);
            LayoutConfig config = mHeaderContainer.getLayoutConfig();
            if (config == null) {
                config = new ListContainer.LayoutConfig(width, height);
            }
            config.width = width;
            config.height = height;
            mHeaderContainer.setLayoutConfig(config);
            mHeaderHeight = height;
        }
    }

    /**
     * 设置顶部区域是否进行缩放动画
     *
     * @param zoomable true：允许缩放；false：不进行缩放动画
     */
    public void setZoomable(boolean zoomable) {
        if (mShowHeaderImage) {
            mZoomable = zoomable;
        }
    }

    /**
     * 判断顶部区域是否随列表滚动进行缩放动画
     *
     * @return true :允许图片缩放；false：不允许图片缩放
     */
    public boolean isZoomable() {
        return mZoomable;
    }

    /**
     * 设置是否拖动图片，只在显示图片并且图片高度高于顶部布局高度才有效
     *
     * @param scrollable true : 允许滚动图片； false：不允许滚动
     */
    public void setScrollable(boolean scrollable) {
        if (mShowHeaderImage) {
            mScrollable = scrollable;
        }
    }

    /**
     * 图片是否允许拖动，默认true
     *
     * @return true:可滚动； false：不可滚动
     */
    public boolean isScrollable() {
        return mScrollable;
    }

    private void removeFixedComponentInfo(Component component, ArrayList<FixedComponentInfo> where) {
        int len = where.size();
        for (int i = 0; i < len; ++i) {
            FixedComponentInfo info = where.get(i);
            if (info.component == component) {
                where.remove(i);
                break;
            }
        }
    }

    /**
     * 隐藏顶部图片
     */
    public void hideHeaderImage() {
        mShowHeaderImage = false;
        mZoomable = false;
        mScrollable = false;
        if (mHeaderProvider != null){
            mHeaderProvider.removeHeader(mHeaderContainer);
            mHeaderProvider.notifyDataChanged();
        }
        removeFixedComponentInfo(mHeaderContainer, mHeaderComponentsInfos);

    }

    private void reset() {
        mActivePointerId = INVALID_VALUE;
        mLastMotionY = INVALID_VALUE;
        mMaxScale = INVALID_VALUE;
        mLastScale = INVALID_VALUE;
    }

    private void onSecondaryPointerUp(TouchEvent ev) {
        int pointerIndex = ev.getIndex();
        HiLog.info(LOG_LABEL, "pointerIndex  : " + pointerIndex);
        if (ev.getPointerId(pointerIndex) == mActivePointerId) {
            int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastMotionY = ev.getPointerScreenPosition(newPointerIndex).getY();
            mActivePointerId = ev.getPointerId(newPointerIndex);
        }
    }

    private void endScaling() {
        if (mHeaderContainer.getBottom() >= mHeaderHeight) {
            HiLog.info(LOG_LABEL, "endScaling:" + mHeaderContainer.getBottom() + "," + mHeaderHeight);
            mAnimationHandler.startAnimation(180);
            mAnimationHandler.sendEvent(0);
        }
    }

    @Override
    public void onContentScrolled(
            Component component, int newPositionX, int newPositionY, int oldPositionX, int oldPositionY) {
        if (mScrollable) {
            HiLog.info(LOG_LABEL, "mScrollable : " + mScrollable);
            float scrollY = (float) (mHeaderHeight - mHeaderContainer.getBottom());
            if (scrollY > 0.0f && scrollY < ((float) mHeaderHeight)) {
                mHeaderImage.scrollTo(0, -((int) (((double) scrollY) * 0.65d)));
            } else if (mHeaderImage.getScrollValue(Component.AXIS_Y) != 0) {
                mHeaderImage.scrollTo(0, 0);
            }
        }
        if (mScrolledListener != null) {
            HiLog.info(LOG_LABEL, "mOnScrollListener != null ~~~~~~~~~~~~~~~~~~~~~~~~~");
            mScrolledListener.onContentScrolled(component, newPositionX, newPositionY, oldPositionX, oldPositionY);
        }
        HiLog.info(
                LOG_LABEL,
                "onContentScrolled : "
                        + component
                        + ":"
                        + newPositionX
                        + ","
                        + newPositionY
                        + ","
                        + oldPositionX
                        + ","
                        + oldPositionY);
    }

    /**
     * 设置滚动监听
     *
     * @param listener 列表滚动监听对象
     */
    public void setScrollListener(ScrolledListener listener) {
        mScrolledListener = listener;
    }
}
